package com.angle.im.server.service;

import com.aliyun.openservices.shade.com.alibaba.fastjson.JSON;
import com.aliyun.openservices.shade.com.alibaba.fastjson.JSONObject;
import com.angle.im.common.domain.constants.Action;
import com.angle.im.common.msg.Message;
import com.angle.im.common.msg.SendToOne;
import com.angle.im.server.config.ws.CustomSpringConfigurator;
import com.angle.im.server.domain.Client;
import com.angle.im.server.domain.ImServer;
import com.angle.im.server.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

/**
 * @author zhangwei
 * websocket服务定义和事件监控
 * 暂时未实现自定义的编解码类
 */
@ServerEndpoint(value = "/socketServer/{token}", configurator = CustomSpringConfigurator.class)
@Component
@Slf4j
public class SocketServer {

    @Autowired
    private ServerService serverService;
    @Autowired
    private MessageService messageService;

    private final static Message PONG = new Message<>(Action.HEART_BEAT);

    /**
     * 用户连接时触发，我们将其添加到
     * 保存客户端连接信息的socketServers中
     *
     * @param session
     * @param token
     */
    @OnOpen
    public void open(Session session, @PathParam(value = "token") String token) {
        String userId = JWTUtils.getInstance().getSubject(token);
        String platform = JWTUtils.getInstance().getPlatform(token);
        //将用户id和服务器id进行绑定
        serverService.bindUserId(platform, userId, session);
        log.info("客户端:【{}】连接成功,当前在线人数:{}", userId, ImServer.INSTANCE.localUserNum.get());
    }

    /**
     * 收到客户端发送信息时触发
     *
     * @param msg
     */
    @OnMessage
    public void onMessage(String msg, Session mySession) {
        Message message = JSON.parseObject(msg, Message.class);
        /**
         * ****************************************************************
         * **        注:  目前由于不做用户间聊天,所以只做心跳检测        **
         * ****************************************************************
         * 如果是心跳检测
         *      返回心跳值
         *      心跳检测的pong部分应由服务器主动遍历所有session发送,而不是在ping后被动发送pong
         * 如果是向指定用户发消息
         *      先根据目标用户id获取session,然后像此session发送消息
         * 如果是向某群组发消息,
         *      先获取群组下用户id,在获取对应session,然后发送
         * 如果像所有人发送
         *      直接遍历session,发送
         */
        if (message.getAction() == Action.HEART_BEAT) {
            try {
                mySession.getBasicRemote().sendText(JSON.toJSONString(PONG));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else if (message.getAction() == Action.TO_ONE){
            JSONObject jsonObject = (JSONObject) message.getBody();
            SendToOne sendToOne = JSONObject.parseObject(jsonObject.toJSONString(),SendToOne.class);
            String fromUser = ImServer.INSTANCE.getUserId(mySession);
            Client client = ImServer.INSTANCE.userClientMap.get(sendToOne.getTo());
            sendToOne.setFrom(fromUser);
            if (client.getSession() != null && client.getSession().isOpen()){
                try {
                    client.getSession().getBasicRemote().sendText(JSON.toJSONString(sendToOne));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //正常使用用注释里的,这里是为了演示本地消息发送  所以使用上面的部分
//            messageService.sendOne(,sendToOne.getFrom(),sendToOne.getTo(),sendToOne.getMsg());

        }
    }

    /**
     * 连接关闭触发，通过sessionId来移除
     * socketServers中客户端连接信息
     */
    @OnClose
    public void onClose(Session session) {
        ImServer.INSTANCE.userClientMap.forEach((userId, client) -> {
            if (client.getSession().getId().equals(session.getId())) {
                ImServer.INSTANCE.localUserNum.getAndDecrement();
                log.info("客户端:【{}】断开连接,当前在线:{}", client.getUserId(), ImServer.INSTANCE.localUserNum.get());
                ImServer.INSTANCE.userClientMap.remove(userId);
            }
        });
    }

    /**
     * 发生错误时触发
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error, Session session) {
        error.printStackTrace();
        log.error("客户端:【{}】发生异常", session.getId());
    }

    /**
     * 向指定用户发送消息
     *
     * @param from 发送人
     * @param to   接收人
     * @param msg  消息内容
     */
    @Async("imExecutor")
    public void sendToOne(String from, String to, String msg) {
        SendToOne sendToOne = new SendToOne(from,to,msg);
        sendMessage(to, JSON.toJSONString(sendToOne));
    }

    /**
     * 信息发送的方法，通过客户端的userName
     * 拿到其对应的session，调用信息推送的方法
     *
     * @param message
     * @param userId
     */
    private synchronized void sendMessage(String userId, String message) {
        sendMessage(getSession(userId), message);
        log.info("客户端【{}】收到消息:{}", userId, message);
    }

    /**
     * 发送消息
     *
     * @param session
     * @param text
     */
    private synchronized void sendMessage(Session session, String text) {
        if (session != null && session.isOpen()) {
            //使用异步  提升效率
            session.getAsyncRemote().sendText(text);
        }
    }

    /**
     * 获取服务端当前客户端的连接数量，
     *
     * @return
     */
    public synchronized long getOnlineNum() {
        return ImServer.INSTANCE.localUserNum.get();
    }

    /**
     * 信息群发
     *
     * @param message
     */
    public synchronized void sendAll(String message) {
        ImServer.INSTANCE.userClientMap.forEach((userId, client) -> {
            try {
                client.getSession().getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        log.info("服务端推送给所有客户端 :【{}】", message);
    }

    /**
     * 多个人发送给指定的几个用户
     *
     * @param message
     * @param persons
     */
    public synchronized void sendMany(String[] persons, String message) {
        for (String userName : persons) {
            sendMessage(userName, message);
        }
    }

    /**
     * 根据用户id获取session
     * 其实这么获取是有bug的
     * 如果平台1的用户id和平台2的用户id相同,那么可能造成session被覆盖和消息误发送
     * 但短期内由于业务不需要做这种多平台的支持,所以暂不处理此bug
     *
     * @param userId
     * @return
     */
    public Session getSession(String userId) {
        Client client = ImServer.INSTANCE.userClientMap.get(userId);
        if (client == null || client.getSession() == null) {
            ImServer.INSTANCE.userClientMap.remove(userId);
            return null;
        } else {
            return client.getSession();
        }
    }
}
